/*-
 * Copyright (c) 2011, Derek Young
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *        notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *        notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.    IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <windows.h>
#include <tlhelp32.h>
#include <QMutex>
#include <QWaitCondition>
#include <QString>
#include <QStringBuilder>
#include <vector>
#include <algorithm>
#include "dwarfforeman.h"
#include "formanthread.h"

using namespace std;

extern QWaitCondition actionNeeded;
extern dfsettings settings;
extern vector<dfjob *> dfjobs;

HANDLE hDF;
QMutex mutex;

vector<quint32> usedMemory;
map<string, map<string, quint32> > itemCount;
map<quint16, int> pendingCount;
map<string, quint32> itemSubTypes;
vector<string> inorganicMaterials, organicMaterials, otherMaterials, creatureTypes, reactionTypes;

quint32 queuePointer = 0, itemPointer = 0, inorganicPointer = 0, otherPointer = 0,
    organicAllPointer = 0, creatureTypePointer = 0, reactionPointer = 0;

void FormanThread::run()
{
    actionStatus("Connecting..");
    if(!attach()) return;

    while(true)
    {
        countItems();
        settings.logcount = itemCount["wood"]["all"];
        for (unsigned int i = 0; i < dfjobs.size(); i++)
        {
            dfjobs[i]->count = 0;
            for (unsigned int j = 0; j < dfjobs[i]->result.size(); j++)
            {
                dfjobs[i]->count += itemCount[dfjobs[i]->result[j]["type"]][dfjobs[i]->result[j]["material"]];
            }
        }
        for (unsigned int i = 0; i < dfjobs.size(); i++)
        {
            dfjobs[i]->sourcecount = 0;
            for (unsigned int j = 0; j < dfjobs[i]->source.size(); j++)
            {
                dfjobs[i]->sourcecount += itemCount[dfjobs[i]->source[j]["type"]][dfjobs[i]->source[j]["material"]];
            }
        }

        countPending();
        for (unsigned int i = 0; i < dfjobs.size(); i++)
        {
            if (!dfjobs[i]->enabled) continue;
            insertOrder(dfjobs[i]);
            cullOrder(dfjobs[i]);
        }

        actionDone();
        mutex.lock();
        actionNeeded.wait(&mutex);
        mutex.unlock();
    }
}

bool FormanThread::compareJob(const dfjob *job, const quint32 jobptr)
{
    quint8 data[56];
    ReadProcessMemory(hDF, (void *)jobptr, (void *) &data, 56, 0);

    const quint8 type = data[0];
    const quint16 subtype = *((quint16 *)(data+0x04));
    const qint16 matid = *((qint16 *)(data+0x24));
    const qint16 matsubid = *((qint16 *)(data+0x28));
    const qint16 mattype = *((qint16 *)(data + 52));
    if (type == 0xD3)
    {
        string text = readSTLString(jobptr+8);
        if (job->reaction.empty() || text.empty()) return false;
        if (job->reaction == text) return true;
        return false;
    }
    if (job->type == type)
    {
        if ((!job->subtype.empty()) && (subtype != itemSubTypes[job->subtype])) return false;
        if (job->materialType.empty()) return true;
        if ((job->materialType == "plant")   && (mattype == 0x01)) return true; else if (mattype == 0x01) return false;
        if ((job->materialType == "wood")    && (mattype == 0x02)) return true; else if (mattype == 0x02) return false;
        if ((job->materialType == "cloth")   && (mattype == 0x04)) return true; else if (mattype == 0x04) return false;
        if ((job->materialType == "silk")    && (mattype == 0x08)) return true; else if (mattype == 0x08) return false;
        if ((job->materialType == "leather") && (mattype == 0x10)) return true; else if (mattype == 0x10) return false;
        if ((job->materialType == "bone")    && (mattype == 0x20)) return true; else if (mattype == 0x20) return false;
        if ((job->materialType == "shell")   && (mattype == 0x40)) return true; else if (mattype == 0x40) return false;
        if ((job->materialType == "soap")    && (mattype == 0x0100)) return true; else if (mattype == 0x0100) return false;
        if ((job->materialType == "tooth")   && (mattype == 0x0200)) return true; else if (mattype == 0x0200) return false;
        if ((job->materialType == "horn")    && (mattype == 0x0400)) return true; else if (mattype == 0x0400) return false;
        if ((job->materialType == "pearl")   && (mattype == 0x0800)) return true; else if (mattype == 0x0800) return false;
        if ((job->materialType == "yarn")    && (mattype == 0x1000)) return true; else if (mattype == 0x1000) return false;
        if ((job->materialType == "other") && (matid >= 0) && (matid < 20) &&
                (otherMaterials[matid] == job->other)) return true;
        if ((job->materialType == "inorganic") && ((qint16)inorganicMaterials.size() > matsubid) &&
                (matid == 0) && (matsubid != -1) && (inorganicMaterials[matsubid] == job->inorganic)) return true;
    }
    return false;
}

void FormanThread::cullOrder(dfjob *job)
{
    if (job->pending == 0) return;
    quint16 amount = 0;

    if (job->all)
    {
        if ((int)job->pending <= ((int)job->sourcecount - (int)job->target)) return;
        if (((int)job->pending - ((int)job->sourcecount - (int)job->target)) > 10000) return;
        amount = job->pending - (job->sourcecount - job->target);
    }
    else
    {
        if ((job->count + job->pending) <= job->target) return;
        if (((job->count + job->pending) - job->target)  > 10000) return;
        amount = (job->count + job->pending) - job->target;
        if (amount > job->pending) amount = job->pending;
        if (amount <= settings.buffer) return;
        amount -= settings.buffer;
        //actionLog(QString::number(amount));
    }

    //actionLog(QString::number(amount));
    if (amount >= job->pending) amount = job->pending - 1;
    amount = amount / job->stack;
    if (!amount) return;

    quint32 queuebase = readDWord(queuePointer);
    quint32 queuepos = readDWord(queuePointer+4);

    quint32 a = amount;
    for (int jobs = (queuepos-queuebase)/4; jobs > 0; jobs--)
    {

        queuebase = readDWord(queuePointer);
        queuepos = readDWord(queuePointer+4);
        if (a == 0)
        {
            job->pending -= amount;
            return;
        }

        quint32 jobptr = readDWord(queuebase + (jobs * 4)-4);

        if (compareJob(job, jobptr))
        {
            const quint16 remaining = readWord(jobptr+0x38);
            if (remaining > a)
            {
                writeWord(jobptr+0x38,remaining-a);
                writeWord(jobptr+0x3a,remaining-a);
                job->pending -= (amount * job->stack);
                return;
            }

            //actionLog("deleting the sucker");
            a -= remaining;
            int b = ((queuepos-queuebase)/4) - jobs + 1;

            //quint8 data[b*4];
            QByteArray data;
            data.resize(b*4);
            //QVector<quint8> data(b*4);
            ReadProcessMemory(hDF, (void *)(queuebase + (jobs * 4)), (void *)&data, b * 4, 0);
            WriteProcessMemory(hDF,(void *)(queuebase + (jobs *4) -4) , (void *)&data, b * 4, 0);
            writeDWord(queuePointer+4, readDWord(queuePointer+4) - 4);
        }
    }
    job->pending -= amount;
    return;
}

void FormanThread::insertOrder(dfjob *job)
{
    quint16 amount = 0;

    if (job->all)
    {
        if (job->pending >= (job->sourcecount - job->target)) return;
        if (((job->sourcecount - job->target) - job->pending) > 10000) return;
        amount = (job->sourcecount - job->target) - job->pending;
    }
    else
    {
        if ((job->count + job->pending) >= job->target) return;
        if ((job->target - (job->count + job->pending) > 10000)) return;
        amount = job->target - (job->count + job->pending);
        amount += settings.buffer;
    }

    amount = amount / job->stack;
    if (!amount) return;

    if(job->pending > 0)
    {
        quint32 queuebase = readDWord(queuePointer);
        quint32 queuepos = readDWord(queuePointer+4);

        for (int jobs = (queuepos-queuebase)/4; jobs > 0; jobs--)
        {
            quint32 jobptr = readDWord(queuebase + (jobs * 4)-4);
            if (compareJob(job, jobptr))
            {
                const quint16 remaining = readWord(jobptr+0x38) + amount;
                writeWord(jobptr+0x38,remaining);
                writeWord(jobptr+0x3a,remaining);
                job->pending += (amount * job->stack);
                return;
            }
        }
    }

    //Find free memory
    quint32 freeSpot = readDWord(queuePointer) + 0x3000;
    sort(usedMemory.begin(), usedMemory.end());
    for (quint32 i = 0; i < usedMemory.size(); i++) //
    {
        if(usedMemory[i] < freeSpot) continue;
        if(usedMemory[i] == freeSpot) { freeSpot += 64; continue; }
        if(usedMemory[i] > freeSpot) break;
    }
    usedMemory.push_back(freeSpot);

    quint8 data[64] = { 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x6e, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x77, 0x20, 0xff, 0xff,
                         0x75, 0x72, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
                         0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 };

    if (!job->reaction.empty())
    {
        data[0] = 0xD3;
        data[0x18] = job->reaction.size();

        if (job->reaction.size() > 16)
        {
            memcpy (data+8, &job->reactionPtr, 4);
            data[0x1c] = 0x1F;
        }
        else
        {
            const char *blah = job->reaction.c_str();
            memcpy (data+8, blah, job->reaction.size());
            data[0x1c] = 0x0F;
        }
    }
    else
    {
        data[0] = job->type;

        if (job->materialType == "plant")   data[52] = 0x01;
        if (job->materialType == "wood")    data[52] = 0x02;
        if (job->materialType == "cloth")   data[52] = 0x04;
        if (job->materialType == "silk")    data[52] = 0x08;
        if (job->materialType == "leather") data[52] = 0x10;
        if (job->materialType == "bone")    data[52] = 0x20;
        if (job->materialType == "shell")   data[52] = 0x40;
        if (job->materialType == "soap")    data[53] = 0x01;
        if (job->materialType == "tooth")   data[53] = 0x02;
        if (job->materialType == "horn")    data[53] = 0x04;
        if (job->materialType == "pearl")   data[53] = 0x08;
        if (job->materialType == "yarn")    data[53] = 0x10;

        if (job->materialType == "other")
        {
            for (quint16 i = 0; i <= otherMaterials.size(); i++)
            {
                if (i == otherMaterials.size())
                {
                    actionLog("invalid material in dfjobs.xml");
                    return;
                }
                if (otherMaterials[i] == job->other)
                {
                    memcpy(data+36, &i, 2);
                    break;
                }
            }
        }
        if (job->materialType == "inorganic")
        {
            for (quint32 i = 0; i <= inorganicMaterials.size(); i++)
            {
                if (i == inorganicMaterials.size())
                {
                    actionLog("invalid material in dfjobs.xml");
                    return;
                }
                if (inorganicMaterials[i] == job->inorganic)
                {
                    data[36] = 0x00;
                    data[37] = 0x00;
                    memcpy(data+40, &i, 4);
                    break;
                }
            }
        }
    }

    if (!job->subtype.empty()) memcpy (data+4, &itemSubTypes[job->subtype], 2);

    memcpy (data+56, &amount, 2);
    memcpy (data+58, &amount, 2);
    WriteProcessMemory(hDF, (void *) freeSpot, (void *) &data, 64, 0);
    writeDWord(readDWord(queuePointer+4), freeSpot);
    writeDWord(queuePointer+4, readDWord(queuePointer+4)+4);
    job->pending += (amount * job->stack);
    return;
}

void FormanThread::countPending()
{
    quint32 queuebase = readDWord(queuePointer);
    quint32 queuepos = readDWord(queuePointer+4);

    for (unsigned int i = 0; i < dfjobs.size() ; i++)
    {
        dfjobs[i]->pending = 0;
    }

    usedMemory.clear();
    pendingCount.clear();
    for (int jobs = (queuepos-queuebase)/4; jobs > 0; jobs--)
    {
        quint32 job = readDWord(queuebase + (jobs * 4)-4);
        usedMemory.push_back(job);

        /* for test only /////////////////////////////////////////////

        quint8 data[56];
        ReadProcessMemory(hDF, (void *)job, (void *) &data, 56, 0);

        const quint8 type = data[0];
        const quint16 subtype = *((quint16 *)(data+0x04));
        const qint16 matid = *((qint16 *)(data+0x24));
        const qint16 matsubid = *((qint16 *)(data+0x28));
        const qint16 mattype = *((qint16 *)(data + 52));
        actionLog(QString("%1 %2 %3 %4 %5").arg(QString::number(type,10)).arg(QString::number(subtype,16)).arg(QString::number(matid,16)).arg(QString::number(matsubid,16)).arg(QString::number(mattype,16)));

        */ /////////////////////////////////////////////////////

        for (unsigned int z = 0; z < dfjobs.size(); z++)
        {
            if (!dfjobs[z]->enabled) continue;
            if (compareJob(dfjobs[z], job))
            {
                dfjobs[z]->pending += (readWord(job+0x38)  * dfjobs[z]->stack);
                break;
            }
        }
    }
}

void FormanThread::countItems()
{
    itemCount.clear();

    const quint32 itembase = readDWord(itemPointer);
    const quint32 itempos = readDWord(itemPointer+4);
    const quint32 itemSize = itempos-itembase;
    const quint32 numItems = itemSize >> 2;

    const quint32 * __restrict items = (quint32 *) malloc(itemSize);
    ReadProcessMemory(hDF, (void *) itembase, (void *) items, itemSize, 0);

    char className[256] = { 0 };
    for (unsigned int i = 0; i < numItems; i++)
    {
        const quint32 vtable = readDWord(items[i]);
        const quint32 type = readWord(readDWord(vtable)) >> 8;
        const quint32 matoff = readDWord(readDWord(vtable+8)) >> 24 ;
        const qint16 typeC = readWord(items[i]+matoff);
        const qint16 typeD = readWord(items[i]+matoff + (matoff % 4 ? 2:4));
        const unsigned int quantity = readByte(items[i]+0x64);

        // Time to cull the list...
        quint8 statusFlags[4];
        ReadProcessMemory(hDF,(void *)(items[i]+0xC),statusFlags,4,0);
        if((statusFlags[1] == 0xC0) || // Caravan
                ((statusFlags[2] > 0) && (statusFlags[2] != 0x80)) || // Marked forbidden or dumped(but not melt)
                (statusFlags[0] == 2) || // Assigned to a task
                ((statusFlags[1] & 4) == 4) || // A wall or floor or something
                (statusFlags[0] == 32))  // Part of a building
            continue;


        className[0] = '\0';
        ReadProcessMemory(hDF, (void *)(readDWord(readDWord(vtable-0x4)+0xC)+0xC), (void *)&className, 255, 0);
        className[255] = '\0';
        if (strlen(className) > 4) className[strlen(className)-4] = '\0';
        strcpy(className, className+5);
        if (strcmp(className,"corpse") == 0) continue; // corpses are weird
        if (strcmp(className,"remains") == 0) continue; // so are remains lol
        if (strcmp(className,"corpsepiece") == 0) continue; // ... and thier pieces

        if ((strcmp(className,"bin") == 0) || (strcmp(className,"barrel") == 0) || (strcmp(className,"box") == 0))
        {
            const quint32 csize = (readDWord(items[i] + 0x2c) - readDWord(items[i] + 0x28))/sizeof(quint32);
            if (csize > 1) continue;
            if ((csize == 1) && (statusFlags[0] != 0)) continue;
        }

        string subtype;
        switch (type)
        {
        case 13: // instruments
        case 14: // toys
        case 24: // weapons
        case 25: // armor
        case 26: // shoes
        case 27: // shields
        case 28: // helms
        case 29: // gloves
        case 38: // ammo
        case 59: // pants
        case 64: // siege ammo
        case 67: // traps
            subtype = readSTLString(readDWord(items[i]+0xA0)+0x24);
            break;
        case 71: // food
            subtype = readSTLString(readDWord(items[i]+0x90)+0x24);
            break;
        case 30: // box/bags
            if ((typeC != 420) && (typeC >= 19))
                strcpy(className,"bag");
            break;
        }

        string material, materialType, itemName(className);

        if (typeC == 0)
        {
            if ((typeD < 0) || (typeD >= (qint16)inorganicMaterials.size())) { material = "unknown"; materialType = "unknown"; }
            else { material = inorganicMaterials[typeD]; materialType = "inorganic"; }
        }
        else if ((typeC >= 419) && (typeC <= 618))
        {
            if ((typeD < 0) || (typeD >= (qint16)organicMaterials.size())) { material = "unknown"; materialType = "unknown"; }
            else { material = organicMaterials[typeD]; materialType = "organic"; }
        }
        else if ((typeC < 19) && (typeC > 0))
        {
            if ((typeC < 0) || (typeC >= (qint16)otherMaterials.size())) { material = "unknown"; materialType = "unknown"; }
            else { material = otherMaterials[typeC]; materialType = "other"; }
        }
        else if ((typeD == 1) && (typeC >= 0))
        {
            if (typeC >= (qint16)creatureTypes.size()) { material = "unknown"; materialType = "unknown"; }
            else { material = creatureTypes[typeC]; materialType = "creature"; }
        }
        else if (typeD >= 0)
        {
            if (typeD >= (qint16)creatureTypes.size()) { material = "unknown"; materialType = "unknown"; }
            else { material = creatureTypes[typeD]; materialType = "creature"; }
        }
        else
        {
            material = "unknown";
            materialType = "unknown";
        }

        itemCount[itemName][material] += quantity;
        itemCount[itemName][materialType] += quantity;
        itemCount[itemName]["all"] += quantity;

        if(!subtype.empty())
        {
            itemCount[subtype][material] += quantity;
            itemCount[subtype][materialType] += quantity;
            itemCount[subtype]["all"] += quantity;
        }

        if (statusFlags[2] == 0x80)
        {
            itemCount["melt"][material] += quantity;
            itemCount["melt"][materialType] += quantity;
            itemCount["melt"]["all"] += quantity;
        }

    }
    free((quint32 *) items);
}

bool FormanThread::attach()
{
    quint32 dfsize = 0;
    quint32 dfbase = 0;
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof (PROCESSENTRY32);
    const HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (Process32First(hProcessSnap, &pe32))
    {
        do
        {
            if (wcsicmp(TEXT("Dwarf Fortress.exe"), pe32.szExeFile) == 0)
            {
                MODULEENTRY32 me32;
                me32.dwSize = sizeof(MODULEENTRY32);
                const HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pe32.th32ProcessID);
                if (Module32First(hSnapShot, &me32))
                {
                    do
                    {
                        if (wcsicmp(TEXT("Dwarf Fortress.exe"), me32.szModule) == 0)
                        {
                            dfsize = (quint32)me32.modBaseSize;
                            dfbase = (quint32)me32.modBaseAddr;
                            hDF = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
                            CloseHandle(hSnapShot);
                            CloseHandle(hProcessSnap);
                            goto dffound;
                        }
                    } while (Module32Next (hSnapShot, &me32));
                }
                CloseHandle(hSnapShot);
            }
        } while (Process32Next(hProcessSnap, &pe32));
    }

    CloseHandle(hProcessSnap);
    actionPopup("Unable to find Dwarf Fortress.exe!",true);
    return false;

    dffound:
        actionLog(QString("Dwarf Fortress.exe found at 0x" % (QString::number(dfbase,16)) % " with a size of " %
                          QString::number(dfsize) % " bytes."));

    actionStatus("Scanning...");
    const quint8 * __restrict memory8 = (quint8*)malloc(dfsize);
    const quint32 * __restrict memory32 = (quint32*)memory8;
    ReadProcessMemory(hDF, (void *)dfbase, (void *)memory8, dfsize, 0);

    quint32 pe_header_offset = memory8[60] + (memory8[61]<<8);
    quint32 timestamp_offset = (pe_header_offset + 4 + 2 * 2)>>2;
    quint32 timestamp = memory32[timestamp_offset];
    QString checksum = QString::number(timestamp,16);
    actionLog(QString("DF checksum: 0x%1").arg(checksum));


    quint32 *dfvector = 0;
    quint32 trash,rtti,typeinfo = 0;
    char className[256] = {0};
    string tstring;
    quint32 strsize = 0;
    quint32 strmode = 0;
    quint32 size = 0;
    quint32 deleteOperator = 0;

    for (unsigned int a = 0; a < ((dfsize/4)-100); a++)
    {
        //
        const quint32 vectorBase = *(memory32+a);
        const quint32 vectorPos = *(memory32+a+1);
        const quint32 vectorCap = *(memory32+a+2);
        const quint32 vectorNull = *(memory32+a+3);

        if (vectorNull != 0) goto vectorsearchdone;
        if (vectorBase == 0) goto vectorsearchdone;
        if (vectorPos == 0) goto vectorsearchdone;
        if (vectorCap == 0) goto vectorsearchdone;

        if ((vectorBase % 4) != 0) goto vectorsearchdone;
        if ((vectorPos % 4) != 0) goto vectorsearchdone;
        if ((vectorCap % 4) != 0) goto vectorsearchdone;

        if (vectorBase > vectorPos) goto vectorsearchdone;
        if (vectorBase > vectorCap) goto vectorsearchdone;
        if (vectorPos > vectorCap) goto vectorsearchdone;

        size = vectorPos - vectorBase;
        if (size > 100000) goto vectorsearchdone;
        if (size == 0) goto vectorsearchdone;

        dfvector = (quint32 *) malloc(size);
        if(ReadProcessMemory(hDF, (void *)vectorBase, (void *)dfvector, size, 0))
        {
            trash = 0;
            className[0] = '\0';

            for (quint32 c = 0; c < (size / 4); c++)
            {
                if(!ReadProcessMemory(hDF, (void *)*(dfvector+c), (void *)&trash, 4, 0))
                {
                    free ((quint32 *)dfvector);
                    goto vectorsearchdone;
                }
            }

            if(!ReadProcessMemory(hDF, (void *)(*(dfvector)), (void *)&trash, 4, 0)) goto readclassfail;
            if(!ReadProcessMemory(hDF, (void *)(trash - 0x4), (void *)&rtti, 4, 0)) goto readclassfail;
            if(!ReadProcessMemory(hDF, (void *)(rtti + 0xC), (void *)&typeinfo, 4, 0)) goto readclassfail;
            if(!ReadProcessMemory(hDF, (void *)(typeinfo + 0xC), (void *)&className, 255, 0)) goto readclassfail;
            className[255] = '\0';
            if (strlen(className) > 2) className[strlen(className)-2] = '\0';
            if(strncmp(className,"itemdef_",8) == 0)
            {
                //actionLog(QString("item subtype"));
                for (trash = 0; trash < (size / 4); trash++)
                {
                    tstring = readSTLString(*(dfvector+trash) + 0x3c);
                    //actionLog(QString(tstring.c_str()));
                    itemSubTypes[tstring] = readWord(*(dfvector+trash) + 0x20);
                    //actionLog(QString("%1 %2").arg(itemSubTypes[tstring]).arg(tstring.c_str()));
                }
            }
            goto vectorsearchdone;
            readclassfail:

            for (quint32 ipad = 0; ipad < (size / 4); ipad++)
            {
                trash = 0;
                strsize = readDWord(*(dfvector+ipad) + 0x10);
                strmode = readDWord(*(dfvector+ipad) + 0x14);

                if (strsize > 255)
                {
                    free ((quint32 *)dfvector);
                    goto vectorsearchdone;
                }


                if (strmode == 0x0F)
                {
                    ReadProcessMemory(hDF, (void *)*(dfvector+ipad), (void *)&className, strsize, 0);
                    className[strsize] = '\0';
                }

                if (strmode > 0x10)
                {
                    ReadProcessMemory(hDF, (void *)*(dfvector+ipad), (void *)&trash, 4, 0);
                    ReadProcessMemory(hDF, (void *)trash, (void *)&className, strsize, 0);
                    className[strsize] = '\0';
                }

                if ((inorganicPointer == 0) && (strcmp(className,"IRON") == 0))
                {
                    inorganicPointer = (a*4) + dfbase;
                    actionLog(QString("Inorganic Pointer Found: 0x%1 (0x%2)").arg(QString::number(inorganicPointer, 16)).arg(QString::number(inorganicPointer - dfbase + 0x400000, 16)));
                    for (trash = 0; trash < (size / 4); trash++)
                    {
                        tstring = readSTLString(*(dfvector+trash));
                        //actionLog(QString(tstring.c_str()));
                        inorganicMaterials.push_back(tstring);
                    }
                }
                if ((organicAllPointer == 0) && ((strcmp(className,"MEADOW-GRASS") == 0) || (strcmp(className,"MUSHROOM_HELMET_PLUMP") == 0)))
                {
                    organicAllPointer = (a*4) + dfbase;
                    actionLog(QString("Organic All Pointer Found: 0x%1 (0x%2)").arg(QString::number(organicAllPointer, 16)).arg(QString::number(organicAllPointer - dfbase + 0x400000, 16)));
                    for (trash = 0; trash < (size / 4); trash++)
                    {
                        tstring = readSTLString(*(dfvector+trash));
                        //actionLog(QString(tstring.c_str()));
                        organicMaterials.push_back(tstring);
                    }
                }
                if ((creatureTypePointer == 0) && (strcmp(className,"TOAD") == 0))
                {
                    creatureTypePointer = (a*4) + dfbase;
                    actionLog(QString("Creature Type Pointer Found: 0x%1 (0x%2)").arg(QString::number(creatureTypePointer, 16)).arg(QString::number(creatureTypePointer - dfbase + 0x400000, 16)));
                    for (trash = 0; trash < (size / 4); trash++)
                    {
                        tstring = readSTLString(*(dfvector+trash));
                        //actionLog(QString(tstring.c_str()));
                        creatureTypes.push_back(tstring);
                    }
                }
                if ((reactionPointer == 0) && (strcmp(className,"TAN_A_HIDE") == 0))
                {
                    reactionPointer = (a*4) + dfbase;
                    actionLog(QString("Reaction Pointer Found: 0x%1 (0x%2)").arg(QString::number(reactionPointer, 16)).arg(QString::number(reactionPointer - dfbase + 0x400000, 16)));
                    for (trash = 0; trash < (size / 4); trash++)
                    {
                        tstring = readSTLString(*(dfvector+trash));
                        //actionLog(QString(tstring.c_str()));
                        reactionTypes.push_back(tstring);
                        //tstring = readSTLString(*(dfvector+trash)+0x1c);
                        //actionLog(QString(tstring.c_str()));
                    }
                }
            }
            free ((quint32 *)dfvector);
        }
        else
        {
            free ((quint32 *)dfvector);
        }

        vectorsearchdone:

        // not a bug here, the stuff we're looking for is always in the first 1/4th of memory space while vectors can be found later
        if ((memory8[a] == 0x8b)    && (memory8[a+1] == 0x15) && (memory8[a+6] == 0x8b)  && (memory8[a+7] == 0x34) &&
            (memory8[a+8] == 0x8a)  && (memory8[a+9] == 0x85) && (memory8[a+10] == 0xf6) && (memory8[a+11] == 0x74) &&
            (memory8[a+12] == 0x0f) && (memory8[a+13] == 0xe8))
        {
            queuePointer = *((quint32*)(memory8+a+2));
            deleteOperator = *((quint32*)(memory8+a+21)); // 2fer, lol
            actionLog(QString("Queue Pointer Found: 0x%1 (0x%2)").arg(QString::number(queuePointer, 16)).arg(QString::number(queuePointer - dfbase + 0x400000, 16)));
        }

        /* doesn't work with DF2012
        if ((memory8[a] == 0x2b)    && (memory8[a+1] == 0x35) && (memory8[a+6] == 0x57)  && (memory8[a+7] == 0xc1) &&
            (memory8[a+8] == 0xfe)  && (memory8[a+9] == 0x02) && (memory8[a+10] == 0x4e) && (memory8[a+11] == 0x78) &&
            (memory8[a+12] == 0x20) && (memory8[a+13] == 0xbf))
        {
            itemPointer = *((quint32*)(memory8+a+2));
            actionLog("Item Pointer Found: " % QString::number(itemPointer - dfbase + 0x400000, 16));
        }
        */

    }
    if (checksum=="4fb0ccba")
        itemPointer = 0x1829a30 + dfbase - 0x400000;
    else
        itemPointer = 0x182aa30 + dfbase - 0x400000;
    actionLog(QString("Item Pointer Found: 0x%1 (0x%2)").arg(QString::number(itemPointer, 16)).arg(QString::number(itemPointer - dfbase + 0x400000, 16)));
    free((quint8 *)memory8);

    if (queuePointer && itemPointer && inorganicPointer && organicAllPointer &&
            creatureTypePointer && reactionPointer)
    {
        actionLog("All vectors successfully found!");
    }
    else
    {
        actionPopup("Failure finding all vectors! :(", true);
    }

    otherMaterials.push_back("INORGANIC");
    otherMaterials.push_back("AMBER");
    otherMaterials.push_back("CORAL");
    otherMaterials.push_back("GLASS_GREEN");
    otherMaterials.push_back("GLASS_CLEAR");
    otherMaterials.push_back("GLASS_CRYSTAL");
    otherMaterials.push_back("WATER");
    otherMaterials.push_back("COAL");
    otherMaterials.push_back("POTASH");
    otherMaterials.push_back("ASH");
    otherMaterials.push_back("PEARLASH");
    otherMaterials.push_back("LYE");
    otherMaterials.push_back("MUD");
    otherMaterials.push_back("VOMIT");
    otherMaterials.push_back("SALT");
    otherMaterials.push_back("FILTH_B");
    otherMaterials.push_back("FILTH_Y");
    otherMaterials.push_back("UNKNOWN_SUBSTANCE");
    otherMaterials.push_back("GRIME");

    actionLog("Prepping..");

    if(readDWord(queuePointer+12) == 0xDEADBEAF)
    {
        actionLog("DF (already) prepared and ready.");
    }
    else
    {
        const LPVOID lpvResult = VirtualAllocEx(hDF, NULL, 0x20000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
        if(lpvResult == NULL)
        {
            actionPopup("Allocating memory failed.", true);
            return false;
        }
        actionLog("Allocated memory at " % QString::number((quint32)lpvResult, 16));

        const quint32 queuebase = readDWord(queuePointer);
        const quint32 queuepos = readDWord(queuePointer+4);
        const quint32 queuesize = queuepos-queuebase;

        const quint32 * __restrict queue = (quint32 *) malloc(queuesize);
        ReadProcessMemory(hDF, (void*)queuebase, (void*) queue, queuesize, 0);
        WriteProcessMemory(hDF, (void*)lpvResult, (void*) queue, queuesize, 0);
        free ((quint32 *)queue);
        writeDWord(queuePointer, (quint32) lpvResult);
        writeDWord(queuePointer+4, ((quint32) lpvResult) + queuesize);
        writeDWord(queuePointer+8, ((quint32) lpvResult) + 0x0FA0);
        writeDWord(queuePointer+12, 0xDEADBEAF);

        // one last EVIL dead to do. put the free operator at +FB0 and load our own goodie in there.
        quint32 realfree = readDWord(deleteOperator);
        actionLog("Hooking into the free operator at " % QString::number(realfree,16));
        quint32 mystuff = (quint32) lpvResult;
        writeDWord(mystuff+0xFB0, realfree);
        realfree = mystuff+0xFB0;

        quint8 in[45];
        in[0]  = 0x55;                                                              // push ebp
        in[1]  = 0x89; in[2]  = 0xE5;                                               // mov ebp, esp
        in[3]  = 0x83; in[4]  = 0xEC; in[5]  = 0x18;                                // sub esp, 18
        in[6]  = 0xB8; memcpy(in+7, &mystuff, 4);                                   // mov eax our memory
        in[11] = 0x39; in[12] = 0x45; in[13] = 0x08;                                // cmp [ebp+08],eax
        in[14] = 0x72; in[15] = 0x0F;                                               // jump +15 if it isn't our memory
        in[16] = 0xB8; memcpy(in+17, &mystuff, 4);                                  // mov eax our memory
        in[21] = 0x05; in[22] = 0x00; in[23] = 0x00; in[24] = 0x02; in[25] = 0x00;  // add eax 0x2000
        in[26] = 0x3B; in[27] = 0x45; in[28] = 0x08;                                // cmp eax, [ebp+08]
        in[29] = 0x73; in[30] = 0x0C;                                               // jae 12 it is our memory
        in[31] = 0x8B; in[32] = 0x45; in[33] = 0x08;                                // mov eax, [ebp+08]
        in[34] = 0x89; in[35] = 0x04; in[36] = 0x24;                                // mov [esp], eax
        in[37] = 0xFF; in[38] = 0x15; memcpy(in+39, &realfree, 4);                  // use the real free operator
        in[43] = 0xC9;                                                              // leave
        in[44] = 0xC3;                                                              // ret

        WriteProcessMemory(hDF, (void *)(mystuff+0xFC0), (void *) in, 45, 0);
        DWORD oldprotect;
        if(VirtualProtectEx(hDF, (LPVOID) (deleteOperator), 1, PAGE_READWRITE, &oldprotect))
        {
            actionLog("VirtualProtectEX == true");
            if(writeDWord(deleteOperator, mystuff+0xFC0))
                actionLog("writeDWord == true");
            else actionLog("writeDWord == false");
            VirtualProtectEx(hDF, (LPVOID) (deleteOperator), 1, oldprotect, &oldprotect);
        }
        else actionLog("VirtualProtectEX == false");

        actionStatus("DF prepared and ready.");
    }

    // setup strings
    quint32 freeSpot = readDWord(queuePointer) + 0x2000;
    for (unsigned int i = 0; i < dfjobs.size() ; i++ )
    {
        if (dfjobs[i]->reaction.size() > 16)
        {
            dfjobs[i]->reactionPtr = freeSpot;
            writeRaw(freeSpot, dfjobs[i]->reaction.c_str(), dfjobs[i]->reaction.size());
            freeSpot += dfjobs[i]->reaction.size() + 1;
        }
    }

    return true;
}

bool FormanThread::readRaw(const quint32 address, LPVOID data, SIZE_T size)
{
    return(ReadProcessMemory(hDF, (LPCVOID)address, data, size, (SIZE_T)NULL));
}

bool FormanThread::writeRaw(const quint32 address, LPCVOID data, SIZE_T size)
{
    return(WriteProcessMemory(hDF, (LPVOID)address, data, size, (SIZE_T)NULL));
}

const quint32 FormanThread::readDWord(const quint32 address)
{
    quint32 dword = 0;
    ReadProcessMemory(hDF, (LPCVOID)address, (LPVOID)&dword, 4, 0);
    return dword;
}

const quint16 FormanThread::readWord(const quint32 address)
{
    quint16 word = 0;
    ReadProcessMemory(hDF, (LPCVOID)address, (LPVOID)&word, (SIZE_T)2, (SIZE_T)NULL);
    return word;
}

const quint8 FormanThread::readByte(const quint32 address)
{
    quint8 byte = 0;
    ReadProcessMemory(hDF, (LPCVOID)address, (LPVOID)&byte, (SIZE_T)1, (SIZE_T)NULL);
    return byte;
}

bool FormanThread::writeDWord(const quint32 address, const quint32 data)
{
    return(WriteProcessMemory(hDF, (LPVOID)address, (LPCVOID)&data, (SIZE_T)4, (SIZE_T)NULL));
}

bool FormanThread::writeWord(const quint32 address, const quint16 data)
{
    return(WriteProcessMemory(hDF, (LPVOID)address, (LPCVOID)&data, (SIZE_T)2, (SIZE_T)NULL));
}

bool FormanThread::writeByte(const quint32 address, const quint8 data)
{
    return(WriteProcessMemory(hDF, (LPVOID)address, (LPCVOID)&data, (SIZE_T)1, (SIZE_T)NULL));
}

const string FormanThread::readSTLString(const quint32 addr)
{
    const quint32 strsize = readDWord(addr + 0x10);
    const quint32 strmode = readDWord(addr + 0x14);

    if (strsize > 255) return string();
    //char text[strsize+1];
    QByteArray text;
    text.resize(strsize+1);

    if (strmode == 0x0F)
    {
        ReadProcessMemory(hDF, (LPCVOID)addr, (LPVOID)(text.data()), (SIZE_T)strsize, 0);
    }
    else if (strmode > 0x10)
    {
        ReadProcessMemory(hDF, (LPCVOID)readDWord(addr), (LPVOID)(text.data()), (SIZE_T)strsize, (SIZE_T)NULL);
    }
    else
    {
        text[0] = '\0';
    }

    text[strsize] = '\0';
    string stlstring(text);
    return stlstring;
}
